"""
    Class      : Loader
    Description: Program loader, which load configuration variable, db session
"""

import os
import sys
import types

import globals as sys_globals

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.exc import OperationalError

from config import Config
from event_logger import EventLogger
from model import Model
from exception import (
    ConfigurationFileParserError,
    ConfigurationFileSectionError,
    ConfigurationFileKeyError,
    ConfigurationFileValueError,
    ClassNotFoundException,
    DatabaseException,
    ModuleNotFoundException,
)


class Loader(object):

    """
    config:     whole program configuration variable
    """
    config      = None

    """
    _config_sections: configuration sections
    """
    _configuration_sections = ["server", "database"]

    """
    _modules:   whole modules for loader ready to load
    """
    _modules     = None

    """
    _dbSession: database session for SQLAlchemy 
    """
    _dbSession   = None

    """
    _eventLogger: whole program logger
    """
    _eventLogger = None


    """
    constructor: initialize loader
    """
    def __init__(self):
        super(Loader, self).__init__()
        self._config       = None
        self._modules      = None
        self._dbSession    = None
        self._event_logger = None
        self._models       = {}
        self._libraries    = {}
        self._classes      = {}
        return


    """
    initialize: initial loader configuration, dbSession, event logger etc
    """
    def initialize(self):
        """
        Fixed this magic variable later
        """
        self._configuration(sys_globals.configuration_file)
        return

    """
    _configuration: method to read configuration variable from config file
    """
    def _configuration(self, file):
        """
        Get config item from configuration file
        """
        self.config = Config()
        self.config.read_configuration_file(file)            
        configuration_items = self.config.get_all_items()

        """
        Check configuration section server and database
        """
        for configuration_section in self._configuration_sections:
            if not configuration_items.has_key(configuration_section):
                raise ConfigurationFileSectionError("[Core][Loader]Configuration file section %s must be specified" % (configuration_section))

        """
        Initalize event logger
        """
        if not configuration_items["server"].has_key("log_file_path"):
            raise ConfigurationFileKeyError("[Core][Loader]Configuration section %s key %s must be specified" % ("server", "log_file_path"))
        elif len(configuration_items["server"]["log_file_path"]) == 0:
            raise ConfigurationFileValueError("[Core][Loader]Configuration section %s key %s value must be s pecified" % ("server", "log_file_path"))

        if not configuration_items["server"].has_key("logger_sock"):
            raise ConfigurationFileKeyError("[Core][Loader]Configuration section %s key %s must be specified" %("server", "logger_sock"))
        elif len(configuration_items["server"]["logger_sock"]) == 0:
            raise ConfigurationFileValueError("[Core][Loader]Configuration section %s key %s value must be s pecified" % ("server", "logger_sock"))
        self._set_logger(configuration_items['server']['log_file_path'], configuration_items['server']['logger_sock'])

        """
        Initialize database session
        """
        self._databaseSession(configuration_items['database'])
        return


    """
    _dbSession: method to load SQLAlchemy database session 
    """
    def _databaseSession(self, configuration):
        """
        Check configuration variables
        """
        if not configuration.has_key("hostname"):
            raise ConfigurationFileKeyError("[Core][Loader]Configuration section '%s' key '%s' must be specified" % ("database", "hostname"))
        elif len(configuration["hostname"]) == 0:
            raise ConfigurationFileValueError("[Core][Loader]Configuration section '%s' key '%s' value must be s pecified" % ("database", "hostname"))

        if not configuration.has_key("dbname"):
            raise ConfigurationFileKeyError("[Core][Loader]Configuration section '%s' key '%s' must be specified" % ("database", "dbname"))
        elif len(configuration["dbname"]) == 0:
            raise ConfigurationFileValueError("[Core][Loader]Configuration section '%s' key '%s' value must be s pecified" % ("database", "dbname"))

        if not configuration.has_key("charset"):
            raise ConfigurationFileKeyError("[Core][Loader]Configuration section '%s' key '%s' must be specified" % ("database", "charset"))
        elif len(configuration["charset"]) == 0:
            raise ConfigurationFileValueError("[Core][Loader]Configuration section '%s' key '%s' value must be s pecified" % ("database", "charset"))

        if not configuration.has_key("username"):
            raise ConfigurationFileKeyError("[Core][Loader]Configuration section '%s' key '%s' must be specified" % ("database", "username"))
        elif len(configuration["username"]) == 0:
            raise ConfigurationFileValueError("Configuration section '%s' key '%s' value must be s pecified" % ("database", "username"))

        if not configuration.has_key("password"):
            raise ConfigurationFileKeyError("[Core][Loader]Configuration section '%s' key '%s' must be specified" % ("database", "password"))
        elif len(configuration["password"]) == 0:
            raise ConfigurationFileValueError("[Core][Loader]Configuration section '%s' key '%s' value must be s pecified" % ("database", "password"))
 
        if not configuration.has_key("port"):
            raise ConfigurationFileKeyError("[Core][Loader]Configuration section '%s' key '%s' must be specified" % ("database", "port"))

        if not configuration.has_key("retry"):
            raise ConfigurationFileKeyError("[Core][Loader]Configuration section '%s' key '%s' must be specified" % ("database", "retry"))

        if not configuration.has_key("timeout"):
            raise ConfigurationFileKeyError("[Core][Loader]Configuration section '%s' key '%s' must be specified" % ("database", "timeout"))

        try:
            conn_str   = "mysql+mysqldb://%s:%s@%s/%s" % (configuration["username"], configuration["password"], configuration["hostname"], configuration["dbname"])
            engine     = create_engine(conn_str, encoding = configuration["charset"])
            connection = engine.connect()
            Session    = sessionmaker(bind = engine)
            self._dbSession = Session(bind = connection)
        except OperationalError as e:
            self._clean()
            raise DataBbaseException(e)
        return


    """
    model: set models for loader
    note : try catch must use to caller, raise ImportError, AttributeError and keyError
    """
    def model(self, module_name, class_name):
        return self.__load_class(module_name, class_name)


    """
    library: set library for loader
    """
    def library(self, module_name, class_name):
        return self.__load_class(module_name, class_name)


    """
    load class:
    """
    def __load_class(self, module_name, class_name):
        if module_name is None or len(module_name) == 0 or \
            class_name is None or len(class_name) == 0:
            return None
        else:
            class_obj = None
            full_import_path = "%s.%s" % (module_name, class_name)
            try:
                class_obj = getattr(sys.modules[module_name], class_name)
            except KeyError,e:
                raise ModuleNotFoundException("[Core][Loader][Load Class] module %s can not be found" % (module_name))
            except AttributeError,e:
                raise ClassNotFoundException("[Core][Loader][Load Class]: module %s, class %s can not be found." % (module_name, class_name))
        return class_obj
   
 
    """
    _set_logger: set logger for the whole program
    """
    def _set_logger(self, file, socket_path):
        self._eventLogger = EventLogger();
        self._eventLogger.set_logger(file, "", socket_path)
        return True


    """
    get_loader_items: get loader class or items from loader
    """
    def get_loader_items(self):
        items = dict()
        items['config']       = self._config
        items['modules']      = self._modules
        items['dbSession']    = self._dbSession
        items['event_logger'] = self._eventLogger
        items['models']       = self._modules
        items['libraries']    = self._libraries
        return items


    """
    release_resource: resource all resource for loader
    """
    def release_resource(self):
        """
        actually use private method to clean up resource
        """
        self._clean()
        return

    """
    _clean: clean up all plugins in loader class
    """
    def _clean(self):
        """
        release database section
        """
        if self._dbSession is not None:
            self._dbSession.close()
            del self._dbSession
            self._dbSession = None

        """
        release event logger
        """
        if self._eventLogger is not None:
            self._eventLogger.clean();
            del self._eventLogger
            self._eventLogger = None
        return

